iT邦幫忙

2024 iThome 鐵人賽

0
Mobile Development

Flutter 開發實戰 - 30 天逃離新手村系列 第 35 篇

了解 Composited Layer 進行應用程式效能優化

  • 分享至 

  • xImage
  •  

在 Flutter 中,除了 Widget、Element、和 RenderObject 結構外,還有一個 Layer 結構。就像 Element 結構在建構階段產生 RenderObject 結構,RenderObject 結構則在渲染階段產生 Layer 結構,當 RenderObject.isRepaintBoundary 為 true 時會建立 Layer 。參考原始碼

我們用 React 來類比;

  • Widget = React Component,可以組成樹狀結構.
  • Element = React.element
  • RenderObject = 虛擬 DOM
  • Layer = 實際 DOM

也就是 Widget > Element > RenderObject > Layer。

Layer 是耗費效能的。文件說明如下:

使用 Layer 時,它們會導致 Rendering Pipeline 必須切換渲染目標(從一個 Layer 到另一個)。渲染目標的切換可能會刷新 GPU 的命令緩衝區,這表示原本可以批次處理獲得優化的效果會消失。渲染目標的切換還會產生大量記憶體需求,因為 GPU 需要將當前幀的內容從優化寫入的記憶體中局部複製出來,然後在切換回原來的 Layer 時再複製回去。

然而,通過將 UI 的部分內容隔離到獨立的 Layer 中,Flutter 可以在只有 UI 的一小部分發生變化時避免重新渲染整個畫面。Layer 有助於實現流暢的動畫效果,因為它們允許 UI 的某些部分獨立進行動畫,而不影響 UI 的其他部分。

因此,為了保持應用程式的 Layer 最佳化,請遵循以下規則:

  • 如果某個元素需要進行動畫,嘗試將其隔離到一個單獨的 Layer 中,這樣就不會造成過多的額外渲染
  • 另一方面,盡可能保持較少的 Layer 數量。每一個層級都會增加額外的計算和記憶體開銷

那麼具體該如何實現:

1. 避免過度渲染

最簡單的方式就是使用 RepaintBoundary 組件。它繼承自 SingleChildRenderObjectWidget ,表示這個組件有專屬的 RenderObject ,而這個 RenderObject 的 isRepaintBoundary 屬性設定為 true。使用 RepaintBoundary 可以輕鬆地將 UI 的某一部分隔離成獨立的 Layer。

RepaintBoundary(
  child: CircularProgressIndicator() // <- 動畫組件
)

這個範例我們限制了重新渲染的範圍,否則整個繪圖層都會重新渲染。

加入 RepaintBoundary 之後:

值得注意的是預設情況下,AppBar 也會在一個獨立的 Layer 中。如果我們檢查 AppBar 的程式碼,會發現它包含了 AnnotatedRegion ,而 AnnotatedRegion 對應的 RenderObject 的 alwaysNeedsCompositing 設定為 true.

這樣設計的原因是,但我們捲動 Scaffold 的 body 時,AppBar 會固定不動,因此不會重新渲染。如果你自己客製化了自己的 AppBar 別忘了將它隔離在單獨的 Layer.

2. 避免過度 Layer

  1. 儘量避免在圖片使用 Opacity 或 ColorFiltered 組件。請使用 Image 的 color 和 blendMode 參數。更多資訊可以參考 Common mistakes with Images in Flutter。

  2. 不要在動畫中使用 Opacity ,AnimatedOpacity 的效能更好。

  3. 使用 decoration ,避免裁切 clipping。saveLayer 在舊的裝置上特別耗費效能,因為它會建立一個螢幕外的渲染目標,切換這些渲染目標可能耗費 1ms 的時間。即使沒有 saveLayer 裁切依舊非常耗費效能,它會影響後續所有的渲染操作。若想為組件增加形狀建議使用 DecoratedBox 或 Container 的 decoration

  4. 儘量禁止裁切;有些組件預設啟用的裁切,若你確定對於佈局沒有什麼用處,例如組件永遠不會超出 Stack 那麼我們可以關閉

    Stack(
      clipBehavior: Clip.none,
      child: ...
    )
    

    另外,請注意 ListView 預設會為每個子項目加入重新渲染邊界。如果你確定列表的子項目不會各自獨立更新,可以在建構子中加入 addRepaintBoundaries: false 來停用此功能。更多關於優化 ListView 的資訊可以參考 Common mistakes with ListViews in Flutter。

  5. 其他像 Transform、BackdropFilter、ShaderMask 和 Texture 這樣的組件也會建立新的 Layer 可以多加注意。


上一篇
錯誤處理
下一篇
Shorebird - Flutter 版 Code Push
系列文
Flutter 開發實戰 - 30 天逃離新手村 共 38 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言